make use of the new `x-nc-skip-trashbin` header
authorJyrki Gadinger <nilsding@nilsding.org>
Thu, 6 Mar 2025 08:17:40 +0000 (09:17 +0100)
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>
Thu, 6 Mar 2025 08:22:55 +0000 (08:22 +0000)
Signed-off-by: Jyrki Gadinger <nilsding@nilsding.org>
src/libsync/basepropagateremotedeleteencrypted.cpp
src/libsync/deletejob.cpp
src/libsync/deletejob.h
src/libsync/discovery.cpp
src/libsync/discoveryphase.cpp
src/libsync/discoveryphase.h
src/libsync/propagateremotedelete.cpp
src/libsync/propagateremotedeleteencryptedrootfolder.cpp
src/libsync/syncfileitem.h

index 05c0ad9ed2b0579bb15fa958d10dbabdb8409b89..17e5d95aa4828ae3620df93f26fc34f5192a0977 100644 (file)
@@ -154,6 +154,7 @@ void BasePropagateRemoteDeleteEncrypted::deleteRemoteItem(const QString &filenam
     qCInfo(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Deleting nested encrypted item" << filename;
 
     const auto deleteJob = new DeleteJob(_propagator->account(), _propagator->fullRemotePath(filename), {}, this);
+    deleteJob->setSkipTrashbin(true);
     if (_encryptedFolderMetadataHandler && _encryptedFolderMetadataHandler->folderMetadata()
         && _encryptedFolderMetadataHandler->folderMetadata()->isValid()) {
         deleteJob->setFolderToken(_encryptedFolderMetadataHandler->folderToken());
index f81c83c87f2a6654186887c8a4574597cc1e6760..67a79756d79bc9d73f6cae7595581ecdad8ca705 100644 (file)
@@ -44,6 +44,10 @@ void DeleteJob::start()
         req.setRawHeader(oneHeaderIt.key(), oneHeaderIt.value());
     }
 
+    if (_skipTrashbin) {
+        req.setRawHeader("X-NC-Skip-Trashbin", "true");
+    }
+
     if (_url.isValid()) {
         startRequest("DELETE", _url, req);
     } else {
@@ -61,4 +65,14 @@ void DeleteJob::setFolderToken(const QByteArray &folderToken)
     _folderToken = folderToken;
 }
 
+bool DeleteJob::skipTrashbin() const
+{
+    return _skipTrashbin;
+}
+
+void DeleteJob::setSkipTrashbin(bool skipTrashbin)
+{
+    _skipTrashbin = skipTrashbin;
+}
+
 } // namespace OCC
index d244e5a85ab70602da78b36e6863edc1b8b10a4b..b796556e075314dc602d4afad9a1a29a201cf501 100644 (file)
@@ -35,9 +35,13 @@ public:
     [[nodiscard]] QByteArray folderToken() const;
     void setFolderToken(const QByteArray &folderToken);
 
+    [[nodiscard]] bool skipTrashbin() const;
+    void setSkipTrashbin(bool skipTrashbin);
+
 private:
     QMap<QByteArray, QByteArray> _headers = {};
     QUrl _url; // Only used if the constructor taking a url is taken.
     QByteArray _folderToken;
+    bool _skipTrashbin = false;
 };
 }
index 6bb6b75e2668bdbb484ca62cdff8f9c305b6dc2c..1ada8a5b59b78aa16788d6df4c55df7dae34cd96 100644 (file)
@@ -1433,6 +1433,11 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
     const bool isOnlineOnlyItem = isCfApiVfsMode && (localEntry.isDirectory || _discoveryData->_syncOptions._vfs->isDehydratedPlaceholder(_discoveryData->_localDir + path._local));
     const auto isE2eeMoveOnlineOnlyItemWithCfApi = isE2eeMove && isOnlineOnlyItem;
 
+    if (isE2eeMove) {
+        qCInfo(lcDisco) << "requesting permanent deletion for" << originalPath;
+        _discoveryData->_permanentDeletionRequests.insert(originalPath);
+    }
+
     if (isE2eeMoveOnlineOnlyItemWithCfApi) {
         item->_instruction = CSYNC_INSTRUCTION_NEW;
         item->_direction = SyncFileItem::Down;
@@ -1784,6 +1789,7 @@ void ProcessDirectoryJob::processFileFinalize(
         job->setInsideEncryptedTree(isInsideEncryptedTree() || item->isEncrypted());
         if (removed) {
             job->setParent(_discoveryData);
+            _discoveryData->_deletedItem[path._original] = item;
             _discoveryData->enqueueDirectoryToDelete(path._original, job);
         } else {
             connect(job, &ProcessDirectoryJob::finished, this, &ProcessDirectoryJob::subJobFinished);
index 0114127ff7207ec767c74094e7d3997bc7d8fe74..469dec380165d0ee5f4d49c3fbd4ed31de45774b 100644 (file)
@@ -225,6 +225,28 @@ void DiscoveryPhase::enqueueDirectoryToDelete(const QString &path, ProcessDirect
     }
 }
 
+void DiscoveryPhase::markPermanentDeletionRequests()
+{
+    // since we don't know in advance which files/directories need to be permanently deleted,
+    // we have to look through all of them at the end of the run
+    for (const auto &originalPath : _permanentDeletionRequests) {
+        const auto it = _deletedItem.find(originalPath);
+        if (it == _deletedItem.end()) {
+            qCWarning(lcDiscovery) << "didn't find an item for" << originalPath << "(yet)";
+            continue;
+        }
+
+        auto item = *it;
+        if (!(item->_instruction == CSYNC_INSTRUCTION_REMOVE || item->_direction == SyncFileItem::Up)) {
+            qCWarning(lcDiscovery) << "will not request permanent deletion for" << originalPath << "as the instruction is not CSYNC_INSTRUCTION_REMOVE, or the direction is not Up";
+            continue;
+        }
+
+        qCInfo(lcDiscovery) << "requested permanent server-side deletion for" << originalPath;
+        item->_wantsPermanentDeletion = true;
+    }
+}
+
 void DiscoveryPhase::startJob(ProcessDirectoryJob *job)
 {
     ENFORCE(!_currentRootJob);
@@ -242,6 +264,7 @@ void DiscoveryPhase::startJob(ProcessDirectoryJob *job)
             auto nextJob = _queuedDeletedDirectories.take(_queuedDeletedDirectories.firstKey());
             startJob(nextJob);
         } else {
+            markPermanentDeletionRequests();
             emit finished();
         }
     });
index 855758b1f9047d149586a7c8b54b03a0f200e0d4..0c9edceac1181b3cc0b300f20346c11e0863e003 100644 (file)
@@ -315,6 +315,11 @@ class DiscoveryPhase : public QObject
 
     void enqueueDirectoryToDelete(const QString &path, ProcessDirectoryJob* const directoryJob);
 
+    /// contains files/folder names that are requested to be deleted permanently
+    QSet<QString> _permanentDeletionRequests;
+
+    void markPermanentDeletionRequests();
+
 public:
     // input
     QString _localDir; // absolute path to the local directory. ends with '/'
index b376366073be718f2ed5e2dac39d20f6e63a093d..6557394951907a98ec638989f872637ec8bffb7f 100644 (file)
@@ -69,13 +69,14 @@ void PropagateRemoteDelete::createDeleteJob(const QString &filename)
         }
     }
 
-    qCInfo(lcPropagateRemoteDelete) << "Deleting file, local" << _item->_file << "remote" << remoteFilename;
+    qCInfo(lcPropagateRemoteDelete) << "Deleting file, local" << _item->_file << "remote" << remoteFilename << "wantsPermanentDeletion" << _item->_wantsPermanentDeletion;
 
     auto headers = QMap<QByteArray, QByteArray>{};
     if (_item->_locked == SyncFileItem::LockStatus::LockedItem) {
         headers[QByteArrayLiteral("If")] = (QLatin1String("<") + propagator()->account()->davUrl().toString() + _item->_file + "> (<opaquelocktoken:" + _item->_lockToken.toUtf8() + ">)").toUtf8();
     }
     _job = new DeleteJob(propagator()->account(), propagator()->fullRemotePath(remoteFilename), headers, this);
+    _job->setSkipTrashbin(_item->_wantsPermanentDeletion);
     connect(_job.data(), &DeleteJob::finishedSignal, this, &PropagateRemoteDelete::slotDeleteJobFinished);
     propagator()->_activeJobList.append(this);
     _job->start();
index 55dd4910fa1c5c8519a26e7953cdf713acd99861..513c69d892fb439be26587c0b470610d0c2170a9 100644 (file)
@@ -184,6 +184,7 @@ void PropagateRemoteDeleteEncryptedRootFolder::deleteNestedRemoteItem(const QStr
     auto deleteJob = new DeleteJob(_propagator->account(), _propagator->fullRemotePath(filename), {}, this);
     deleteJob->setFolderToken(folderToken());
     deleteJob->setProperty(encryptedFileNamePropertyKey, filename);
+    deleteJob->setSkipTrashbin(true);
 
     connect(deleteJob, &DeleteJob::finishedSignal, this, &PropagateRemoteDeleteEncryptedRootFolder::slotDeleteNestedRemoteItemFinished);
 
index 71dc973875dac548c923cab699da87d72e0397de..154d13a745becf56ce2ce225bcba966edc193874 100644 (file)
@@ -347,6 +347,10 @@ public:
     bool isPermissionsInvalid = false;
 
     QString _discoveryResult;
+
+    /// if true, requests the file to be permanently deleted instead of moved to the trashbin
+    /// only relevant for when `_instruction` is set to `CSYNC_INSTRUCTION_REMOVE`
+    bool _wantsPermanentDeletion = false;
 };
 
 inline bool operator<(const SyncFileItemPtr &item1, const SyncFileItemPtr &item2)